package correctness;

import static org.junit.Assert.*;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import model.LogicType;
import model.editormodel.EditorModel;
import model.editormodel.Input;
import model.editormodel.LocsiParser;
import model.editormodel.LogicEditorElement;
import model.editormodel.Output;
import model.simulationmodel.SimulationModel;

import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import view.UtilityConstants;
import controller.commoncontroller.UtilityDialogs;

/**
 * Der RecursiveSDTest fügt eine SD-Bauteil immer wieder ein und speichert
 * dieses unter neuen Namen, Konstruktionsbedingt erhöht sich die Anzahl der
 * nicht sichtbaren Elemente. Dies Veränderung wird kontrolliert.
 * 
 * @author Team Locsi
 * 
 */
public class RecursiveSDTest {
	// maximale Anzahl an Elementen die erzeugt werden sollen
	private final static int RECDEPTH = 50;
	// Name des selbstdefinierten Bauteils, dass verwendet werden soll
	// es muss natuerlich sichgestellt sein, dass dieses Bauteil vorhanden ist
	private final static String BASESDNAME = "recBase_JUNIT";
	private final static String TOPSDNAME = "recTop_JUNIT";
	private final static String origin = "current_JUNIT";
	private static String before = "before_JUNIT";
	private static String current = "current_JUNIT";

	// Festlegen des Typs, welche das Kernelement bildet, es ist jeder Typ
	// erlaubt, ausser Bommel und Selbstdefiniert
	private static LogicType base = LogicType.FLIPFLOP;

	// benoetigte Listen
	private static List<Input> inputs = new ArrayList<Input>();
	private static List<Output> outputs = new ArrayList<Output>();
	private static List<Input> baseInputs = new ArrayList<Input>();
	private static List<Output> baseOutputs = new ArrayList<Output>();

	// Speichern der Anzahl der Ein- und Ausgänge des Grundelements
	private static int numberOfinput;
	private static int numberOfoutput;

	// Objekte die benoetigt werden
	private static EditorModel eModel;
	private static SimulationModel sModel;
	private static LocsiParser locsiParser;

	// Name der Schaltung, der beim abspeichern verwendet wird
	private static String name = "recursiveSDTest";

	@BeforeClass
	public static void setUpBeforeClass() throws Exception {
		// nun muss sichergestellt werden, dass Bauteile mit solchen
		// Namen noch nicht vorkommen, d.h. es wird versucht dieses zu entfernen
		File xmlFile = new File(UtilityConstants.SD_DIRECTORY + BASESDNAME
				+ UtilityDialogs.XML_EXTENSION);
		if (xmlFile.exists()) {
			xmlFile.delete();
		}
		xmlFile = new File(UtilityConstants.SD_DIRECTORY + TOPSDNAME
				+ UtilityDialogs.XML_EXTENSION);
		if (xmlFile.exists()) {
			xmlFile.delete();
		}
		xmlFile = new File(UtilityConstants.SD_DIRECTORY + before
				+ UtilityDialogs.XML_EXTENSION);
		if (xmlFile.exists()) {
			xmlFile.delete();
		}
		xmlFile = new File(UtilityConstants.SD_DIRECTORY + current
				+ UtilityDialogs.XML_EXTENSION);
		if (xmlFile.exists()) {
			xmlFile.delete();
		}

		eModel = new EditorModel();
		sModel = new SimulationModel(eModel);
		locsiParser = new LocsiParser(eModel);
		// das Basiselement wird auf das EditorModel hinzugefuegt
		eModel.addLogicElement(base, null, 500, 500);

		// alle vorhandenen Inputs und Outputs sollen registriert werden
		for (LogicEditorElement e : eModel.getCircuitModel()
				.getUnmodifableElementsOnEditor().values()) {
			if (LogicType.ONE_INPUT.contains(e.getType())) {
				numberOfinput = 1;
				baseInputs.add(e.getInputs().get(Input.STD_A));
			}
			if (LogicType.TWO_INPUTS.contains(e.getType())) {
				numberOfinput = 2;
				if (e.getType() == LogicType.FLIPFLOP) {
					baseInputs.add(e.getInputs().get(Input.FF_SET));
					baseInputs.add(e.getInputs().get(Input.FF_RESET));
				} else {
					baseInputs.add(e.getInputs().get(Input.STD_A));
					baseInputs.add(e.getInputs().get(Input.STD_B));
				}
			}
			if (LogicType.THREE_INPUTS.contains(e.getType())) {
				numberOfinput = 3;
				baseInputs.add(e.getInputs().get(Input.SELECT));
				baseInputs.add(e.getInputs().get(Input.STD_A));
				baseInputs.add(e.getInputs().get(Input.STD_B));
			}
			if (LogicType.ONE_OUTPUT.contains(e.getType())) {
				numberOfoutput = 1;
				baseOutputs.add(e.getOutputs().get(Output.STD_O));
			}
			if (LogicType.TWO_OUTPUTS.contains(e.getType())) {
				numberOfoutput = 2;
				baseOutputs.add(e.getOutputs().get(Output.FF_Q));
				baseOutputs.add(e.getOutputs().get(Output.FF_NQ));
			}
		}
		doSwitchesAndClocks(baseInputs, baseOutputs);
	}

	@Before
	public void setUp() throws Exception {

	}

	@Test
	public void test() {
		// Speicherort
		String location = "test" + UtilityConstants.SEPARATOR + "model"
				+ UtilityConstants.SEPARATOR + "editormodel"
				+ UtilityConstants.SEPARATOR + "test_saved_circuits"
				+ UtilityConstants.SEPARATOR + name
				+ UtilityDialogs.XML_EXTENSION;

		// Eingangs wird das erzeugte Basiselement als SD gespeichert
		assertTrue(
				"Die Basisschaltung erfuellt nicht die Eigenschaften einer Komponente",
				eModel.exportComponent(BASESDNAME) == EditorModel.SaveCircuitResponse.SAVE_OK);

		File xmlFileBefore = new File(UtilityConstants.SD_DIRECTORY + before
				+ UtilityDialogs.XML_EXTENSION);

		// beim ersten Durchlauf muss current den Namen des BASESDNAME erhalten
		current = BASESDNAME.toString();

		// Nun wird in Abhängigkeit von der Rekursionstiefe, eine neue Schaltung
		// erzeugt und auf diese das zuvor erzeugte SD-Element geaddet. Dann
		// werden die Ein- und Ausgänge mit Schaltern und Lampen verbunden.
		// Abschließende wird dies SD-Element als current Element gespeichert
		// und das ganze beginnt von vorn.
		for (int r = 0; r < RECDEPTH; ++r) {
			before = current.toString();
			current = origin.toString() + r;
			eModel.newCircuit();
			eModel.addLogicElement(LogicType.SELFDEFINED, before, 500, 500);
			for (LogicEditorElement e : eModel.getCircuitModel()
					.getUnmodifableElementsOnEditor().values()) {
				if (e.getType() == LogicType.SELFDEFINED) {
					inputs.addAll(e.getInputs().values());
					outputs.addAll(e.getOutputs().values());
				}
			}
			doSwitchesAndClocks(inputs, outputs);
			eModel.exportComponent(current);

			// damit der Ordner der selbstdefinierten Bauteile nicht überfüllt
			// wird, werden die für die Zwischenschritte benötigten selbstdef.
			// Bauteile gelöscht
			xmlFileBefore = new File(UtilityConstants.SD_DIRECTORY + before
					+ UtilityDialogs.XML_EXTENSION);
			if (xmlFileBefore.exists()) {
				xmlFileBefore.delete();
			}
			inputs.clear();
			outputs.clear();
		}

		// Zum Schluss wird die fertige Komponent exportiert
		assertTrue(
				"Die Topschaltung erfuellt nicht die Eigenschaften einer Komponente",
				eModel.exportComponent(TOPSDNAME) == EditorModel.SaveCircuitResponse.SAVE_OK);

		// erzeugen der Simulationselemente
		sModel.updateSimulationElements();

		// ueberpruefen ob die Anzahl der erzeugten Elemente mit der
		// Rekursionstiefe übereinstimmen
		assertTrue(
				"Die Anzahl der internen Elemente stimmt nicht mit der Rekursionstiefe überein",
				(numberOfinput + numberOfoutput) * (RECDEPTH + 1) + 1 == sModel
						.getSimulationElements().size());

		// die Anzahl der auf der obersten Schicht liegenden Element muss gleich
		// der Anzahl der Eingänge + Ausgänge des Basiselements + sich selber
		// ergeben
		assertTrue(
				"Die Anzahl der Elemente auf der obersten Schicht ist falsch",
				(numberOfinput + numberOfoutput + 1) == sModel
						.getUpperSimulationElements().size());

		// Die Schaltung wird im Standardordner abgespeichert um auftretende
		// Bugs leichter finden zu können, da sich die Konstellation bei jedem
		// Aufruf ändert
		assertTrue(
				"Schaltung konnte nicht gespeichert werden",
				locsiParser.saveCircuit(name, location,
						eModel.getCircuitModel()));

		eModel.newCircuit();
		// Abschließend wird noch getestet ob das TOPSDELEMENT wieder importiert
		// werden könnte
		assertNotNull("Das TopSDElement konnt nicht geladen werden",
				eModel.addLogicElement(LogicType.SELFDEFINED, TOPSDNAME, 500, 500));
	}

	/**
	 * Diese Methode fuegt an jedem uebergebenen Input einen Schalter und an
	 * jeden uebergebenen Output eine Lampe an
	 * 
	 * @param i2
	 *            <code>List</code> - Liste der Inputs
	 * @param o2
	 *            <code>List</code> - Liste der Outputs
	 */
	private static void doSwitchesAndClocks(List<Input> i2, List<Output> o2) {
		// jeder Eingang soll mit einem Schalter verbunden werden
		for (int i = 1; i <= i2.size(); ++i) {
			eModel.addLogicElement(LogicType.SWITCH, null, 200, i * 100 + 50);
		}
		// jeder Ausgang soll mit einer Lampe verbunden werden
		for (int i = 1; i <= o2.size(); ++i) {
			eModel.addLogicElement(LogicType.LAMP, null, 800, i * 100 + 50);
		}
		// bestimmen der hinzugefuegten Schalter und Lampen
		for (LogicEditorElement e : eModel.getCircuitModel()
				.getUnmodifableElementsOnEditor().values()) {
			if (e.getType() == LogicType.SWITCH) {
				eModel.setConnection(e.getOutputs().get(Output.STD_O),
						i2.remove(0));
			}
			if (e.getType() == LogicType.LAMP) {
				eModel.setConnection(o2.remove(0),
						e.getInputs().get(Input.STD_A));
			}
		}
	}
}
